/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package drexel.cs544.nap.server;


import java.nio.ByteBuffer;
import java.net.Socket;

import drexel.cs544.nap.NAP;
import drexel.cs544.nap.client.NAPClient;

import java.io.InputStream;
import java.io.OutputStream;
import org.apache.log4j.Logger;

/**
 *
 * @author rch_ltp
 */
public class NAPServerThread extends Thread {

    private Socket _sClient;
    private byte[] _msgBuff = new byte[1024];
    private int _serverVersion = 1;
    private int _serverHostKey = 1234567;
    private ByteBuffer _msgOutBuff = ByteBuffer.allocate(1024);
    private ClientNode _client;

    public NAPServerThread(Socket sClient)
    {
        super("NAPServerThread");
        _sClient = sClient;
        _client = null;
    }

    public ClientNode getClient()
    {
        return _client;
    }

    @Override
    public void run()
    {
        //TODO Add a loop which listens for data and processes it
        //through the protocol
        OutputStream out = null;
        InputStream in = null;
        try
        {
            //open connection streams
            out = _sClient.getOutputStream(); //write to client
            in = _sClient.getInputStream();     //read from client

            // LISTEN State
            int lenConnectReq = NAP.Message.MSG_CLIENT_CONNECTION_REQUEST.Length();
            if (lenConnectReq == in.read(_msgBuff)) {
                // Check Message ID
                if (_msgBuff[0] != NAP.Message.MSG_CLIENT_CONNECTION_REQUEST.Value()) {
                    // Connection Error!
                    Logger.getLogger(NAPServerThread.class).info("Connection " +
                            "Error: Invalid Connection Request Message");
                }
                else {
                    // Check Version
                    int clientVersion = byteArrayToInt(_msgBuff, 1);
                    // Store Client ID
                    int clientID = byteArrayToInt(_msgBuff, 5);
                    // Set Packet Size
                    int maxPacketSize = byteArrayToInt(_msgBuff, 9);
                    // Set Window Size
                    int windowSize = byteArrayToInt(_msgBuff, 13);

                    //Create client Node
                    _client = new ClientNode(clientID, clientVersion, maxPacketSize, windowSize);
                    // Send MSG_CLIENT_CONNECTION_ACCEPT
                    _msgOutBuff.clear();
                    _msgOutBuff.put(NAP.Message.MSG_CLIENT_CONNECTION_ACCEPT.Value());
                    _msgOutBuff.putInt(_serverVersion);
                    _msgOutBuff.putInt(_client.getMaxPacketSize());
                    _msgOutBuff.putInt(_client.getWindowSize());
                    out.write(_msgOutBuff.array(), 0, 13);
                }
            }
            else {
                // Connection Error
                Logger.getLogger(NAPServerThread.class).info("Connection Error: Invalid Connection Request Message");
                _msgOutBuff.clear();
                _msgOutBuff.put(NAP.Message.MSG_CLIENT_CONNECTION_ERROR.Value());
                _msgOutBuff.putInt(2); // CLIENT_CONNECTION_ERROR_CONNECT_FAILED
                _msgOutBuff.put("CLIENT_CONNECTION_ERROR_CONNECT_FAILED: Invalid Connection Request Message".getBytes());
                out.write(_msgOutBuff.array(), 0, _msgOutBuff.position());


                //DO WE NEED TO SEND A MSG_DISCONNECT TO THE CLIENT??
            }

            // Find the Client in the Group Assignments Table
            if (!NAPServer.getInstance().containsClient(_client)) {
                // If the ID is not in the table then we need to
                // ask permission to add it.
            	
            	Group group = NAPServer.getInstance().getNextGroup();
            	group.addClient(_client);
                int groupID = group.getGroupID();

                _msgOutBuff.clear();
                _msgOutBuff.put(NAP.Message.MSG_SERVER_REQUEST_TO_ADD_CLIENT.Value());
                _msgOutBuff.putInt(_serverVersion);
                _msgOutBuff.putInt(_serverHostKey);
                _msgOutBuff.putInt(groupID);
                _msgOutBuff.putInt(0);
                out.write(_msgOutBuff.array(), 0, 17);

                // Get the response, either ACK or NACK
                int lenAddACK = NAP.Message.MSG_CLIENT_ADD_ACK.Length();
                if (lenAddACK == in.read(_msgBuff, 0, lenAddACK)) {
                    // If we received an ACK and the Request ID is correct
                    if (_msgBuff[0] == NAP.Message.MSG_CLIENT_ADD_ACK.Value()
                            && byteArrayToInt(_msgBuff, 1) == 0) {
                        // Add the new clsient node
                        _client.setGroupID(groupID);
                        NAPServer.getInstance().addClient(_client);
                    }
                }
            }

            // END LISTEN STATE

            // BEGIN CONFIGURE STATE
            while(_client.getState() == NAP.State.Config)
            {
                _msgOutBuff.clear();
                _msgOutBuff.put(NAP.Message.MSG_SERVER_CONFIGURATION_REQUEST.Value());

                //NEED TO CREATE CONFIGURATION ID
                int configID = (int)(Math.random()*100);
                String addMessage = "test string";
                _msgOutBuff.putInt(configID);
                _msgOutBuff.put(addMessage.getBytes());

                out.write(_msgOutBuff.array(), 0, _msgOutBuff.position());

                // Get the response, either ACK or NACK
                int lenConfigACK = in.read(_msgBuff, 0, NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Length());

                if (lenConfigACK == NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Length()
                    && _msgBuff[0] == NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Value()
                    && byteArrayToInt(_msgBuff, 1) == configID)
                {

                    Logger.getLogger(NAPServerThread.class).info("Sent Config String: " + addMessage);
            
                    _msgOutBuff.clear();
                    _msgOutBuff.put(NAP.Message.MSG_SERVER_CONFIGURATION_COMPLETE.Value());
                    _msgOutBuff.putInt(configID);
                    out.write(_msgOutBuff.array(), 0, _msgOutBuff.position());

                    Logger.getLogger(NAPServerThread.class).info("Sent END Config");

                    // Get the response, either ACK or NACK
                    int lenConfigCompACK = in.read(_msgBuff, 0, NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Length());
                    if (lenConfigCompACK == NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Length()
                            && _msgBuff[0] == NAP.Message.MSG_CLIENT_CONFIGURATION_ACK.Value()
                            && byteArrayToInt(_msgBuff, 1) == configID) {

                        // CONFIGURATION WAS COMPLETE
                        _client.setState(NAP.State.Data);
                    }
                    else { 
                        // Configuration Error... send disconnect
                        _msgOutBuff.clear();
                        _msgOutBuff.put(NAP.Message.MSG_DISCONNECT.Value());
                        _msgOutBuff.putInt(_serverHostKey);
                        out.write(_msgOutBuff.array(), 0, _msgOutBuff.position());
                        return;
                    }
                }
            }
            // END CONFIGURE STATE


            //keep connection alive
            while(_client.isConnected() && _client.getState() == NAP.State.Data)
            {
                //KEEP LISTENING FOR DATA
            	in.read(_msgBuff, 0, NAP.Message.MSG_DATA_MESSAGE.Length());
            	if (_msgBuff[0] == NAP.Message.MSG_DATA_MESSAGE.Value()) {
                    int dataType = byteArrayToInt(_msgBuff, 5);
                    if (_client.getClientID() == byteArrayToInt(_msgBuff, 5)) {
	                    if (_client.getGroupID() == byteArrayToInt(_msgBuff, 9)) {
		                    int dataID = byteArrayToInt(_msgBuff, 13);
		                    ByteBuffer bb = ByteBuffer.allocate(_msgBuff.length);
		                    String data = (bb.put(_msgBuff, 5, _msgBuff.length-17)).toString();
	                        NAPServer.getInstance().addClient(_client);
	                        Logger.getLogger(NAPServerThread.class).info("Server successfully received data.");
	                        
	                        _msgOutBuff.clear();
	                        _msgOutBuff.put(NAP.Message.MSG_DATA_ACK.Value());
	                        _msgOutBuff.putInt(dataID);
	                        out.write(_msgOutBuff.array(), 0, _msgOutBuff.position());
	                    }
                	}	
                } else if (_msgBuff[0] == NAP.Message.MSG_DISCONNECT.Value()) {
                	_client.setState(NAP.State.Closed);
                }
            }

            out.close();
            in.close();

        }
        catch (Exception ex) {
            ex.printStackTrace();
 
        }
    }

    private static int byteArrayToInt(byte[] b, int offset) {
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (4 - 1 - i) * 8;
            value += (b[i + offset] & 0x000000FF) << shift;
        }
        return value;
    }
}